package repository

import (
	"context"
	"go-admin/server/internal/domain"
	"go-admin/server/internal/repository/dao"
	"go.opentelemetry.io/otel"
	"strings"
	"time"
)

var (
	ErrRoleDuplicate = dao.ErrRoleDuplicate
)

type RbacRepository interface {
	CreateRole(ctx context.Context, ard domain.RoleData) error
	UpdateRole(ctx context.Context, erd domain.RoleData) error
	DelRole(ctx context.Context, roleId int32) error
	RoleLst(ctx context.Context) ([]domain.RoleData, error)

	GetAuthData(ctx context.Context, roleId int32) ([]domain.RoleMenuData, error)
	AuthOperation(ctx context.Context, data domain.AuthData) error
	GetAdminRoleData(ctx context.Context) ([]domain.CreateAdminSelectRoleLst, error)
	GetAuthItemByRoleIds(ctx context.Context, RoleIds []int32) ([]int64, error)
}

type rbacRepository struct {
	rbac dao.RbacDao
	menu dao.MenuDao
}

func NewRbacRepository(rbac dao.RbacDao, menu dao.MenuDao) RbacRepository {
	return &rbacRepository{
		rbac: rbac,
		menu: menu,
	}
}
func (r *rbacRepository) CreateRole(ctx context.Context, ard domain.RoleData) error {
	return r.rbac.CreateRole(ctx, r.domainToEntity(ard))
}

func (r *rbacRepository) UpdateRole(ctx context.Context, erd domain.RoleData) error {
	return r.rbac.UpdateRole(ctx, r.domainToEntity(erd))
}

func (r *rbacRepository) DelRole(ctx context.Context, roleId int32) error {
	return r.rbac.DelRole(ctx, roleId)
}

func (r *rbacRepository) RoleLst(ctx context.Context) ([]domain.RoleData, error) {
	lst, err := r.rbac.RoleLst(ctx)
	if err != nil {
		return []domain.RoleData{}, err
	}
	var result []domain.RoleData
	for _, menuData := range lst {
		rule := r.entityToDomain(menuData)
		result = append(result, rule)
	}
	return result, nil
}

func (r *rbacRepository) GetAuthData(ctx context.Context, roleId int32) ([]domain.RoleMenuData, error) {
	data, err := r.menu.GetMenuData(ctx)
	if err != nil {
		return nil, err
	}

	roleAuth, err := r.rbac.GetRoleAuthData(ctx, roleId)
	type authRuleInfo struct {
		IsRead int32
		MenuID int64
	}
	authRolesMap := make(map[string]authRuleInfo)
	for _, authRule := range roleAuth {
		authRolesMap[authRule.MenuUrl] = authRuleInfo{
			IsRead: authRule.IsRead,
			MenuID: authRule.MenuId,
		}
	}

	for k, d := range data {
		if d.MenuUrl != "" {
			values := strings.Split(d.MenuUrl, ",")
			for _, value := range values {
				if roleData, ok := authRolesMap[value]; ok {
					if roleData.MenuID == d.ID {
						data[k].Checked = true
						data[k].IsRead = roleData.IsRead
					} else {
						data[k].Checked = false
						data[k].IsRead = 1
					}
				} else {
					data[k].Checked = false
					data[k].IsRead = 1
				}
			}
		}
	}

	menuTree := r.menu.BuildMenuTree(data, 0)
	var result []domain.RoleMenuData
	for _, menuData := range menuTree {
		menu := r.entityToDomainMenuTree(menuData)
		result = append(result, menu)
	}
	return result, nil
}

func (r *rbacRepository) AuthOperation(ctx context.Context, data domain.AuthData) error {
	var req []dao.AuthItem
	for _, auth := range data.Auth {
		authData := r.domainToEntityAuthItem(auth)
		req = append(req, authData)
	}
	return r.rbac.Auth(ctx, data.RoleID, req)
}

func (r *rbacRepository) GetAdminRoleData(ctx context.Context) ([]domain.CreateAdminSelectRoleLst, error) {
	lists, err := r.rbac.RoleLst(ctx)
	result := make([]domain.CreateAdminSelectRoleLst, len(lists))
	for i, list := range lists {
		result[i] = r.entityToAdminRoleDomain(list)
	}
	return result, err
}

func (r *rbacRepository) GetAuthItemByRoleIds(ctx context.Context, RoleIds []int32) ([]int64, error) {
	ctx, span := otel.Tracer("左侧菜单").Start(ctx, "Rbac Repository层获取当前用户的角色信息")
	defer span.End()
	return r.rbac.GetAuthItemByRoleIds(ctx, RoleIds)
}

func (r *rbacRepository) domainToEntity(rd domain.RoleData) dao.AuthRole {
	return dao.AuthRole{
		Id:          rd.RoleID,
		Name:        rd.RoleName,
		Description: rd.Description,
		CreateTime:  rd.CreateTime.UnixMilli(),
	}
}

func (r *rbacRepository) entityToDomain(du dao.AuthRole) domain.RoleData {
	return domain.RoleData{
		RoleID:      du.Id,
		Status:      du.Status,
		RoleName:    du.Name,
		Description: du.Description,
		CreateTime:  time.UnixMilli(du.CreateTime),
		UpdateTime:  time.UnixMilli(du.UpdateTime),
	}
}

func (r *rbacRepository) entityToDomainMenuTree(md dao.Menu) domain.RoleMenuData {
	return domain.RoleMenuData{
		MenuID:   int32(md.ID),
		Level:    md.MenuLevel,
		Pid:      int32(md.Pid),
		Title:    md.MenuTitle,
		Checked:  md.Checked,
		IsRead:   md.IsRead,
		Value:    md.MenuUrl,
		Children: r.convertMenus(md.Children),
	}
}

func (r *rbacRepository) convertMenus(daoMenus []dao.Menu) []domain.RoleMenuData {
	var domainMenus []domain.RoleMenuData
	if daoMenus == nil {
		return []domain.RoleMenuData{}
	}
	for _, daoMenu := range daoMenus {
		domainMenu := domain.RoleMenuData{
			MenuID:   int32(daoMenu.ID),
			Level:    daoMenu.MenuLevel,
			Pid:      int32(daoMenu.Pid),
			Title:    daoMenu.MenuTitle,
			Checked:  daoMenu.Checked,
			IsRead:   daoMenu.IsRead,
			Value:    daoMenu.MenuUrl,
			Children: r.convertMenus(daoMenu.Children),
		}
		domainMenu.Children = r.convertMenus(daoMenu.Children)
		domainMenus = append(domainMenus, domainMenu)
	}
	return domainMenus
}

func (r *rbacRepository) domainToEntityAuthItem(i domain.AuthItem) dao.AuthItem {
	return dao.AuthItem{
		RoleID:  int64(i.MenuID),
		MenuUrl: i.AuthURL,
		IsRead:  int32(i.IsRead),
		MenuId:  int64(i.MenuID),
	}
}

func (r *rbacRepository) entityToAdminRoleDomain(du dao.AuthRole) domain.CreateAdminSelectRoleLst {
	return domain.CreateAdminSelectRoleLst{
		RoleID:   du.Id,
		RoleName: du.Name,
	}
}
